/**
 * JAVACC DEMO 1.0
 */
package com.apache.database.datasource;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.sql.DataSource;

import com.apache.tools.ClassToolsUtil;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.jdbc.datasource.lookup.DataSourceLookup;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
import org.springframework.util.Assert;

import com.alibaba.druid.pool.DruidDataSource;
import com.apache.cache.service.impl.LoadCacheFactory;
import com.apache.cache.util.Validator;
import com.apache.rpc.entity.RpcDatasource;
import com.apache.tools.StrUtil;

/**
 * description:
 *
 * @author qihan 创建时间：2017-9-18
 */
public class DynamicDataSource extends AbstractDataSource implements InitializingBean {

	private static DynamicDataSource instance;

	private Map<Object, Object> targetDataSources;

	private Object defaultTargetDataSource;

	private boolean lenientFallback = true;

	private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();

	private Map<Object, DataSource> resolvedDataSources;

	private DataSource resolvedDefaultDataSource;

	private String sysEname;

	private int maxActive = 100;

	private int maxIdle = 20;

	private int minIdle = 10;

	private int initialSize = 10;

	private boolean defaultAutoCommit = false;

	private ScheduledExecutorService scheduExec;

	private Map<String, Long> dynamicTimes = new HashMap<>();

	private String defaultDataSourceKey = "plateform";

	private boolean enableIncludeRpc = false;//是否引入RPC

	private RpcDatasource rpcDb;//rpc数据库文件

	public static DynamicDataSource getInstance() {
		if(instance == null){
			instance = new DynamicDataSource();
		}
		return instance;
	}

	/**
	 * TODO 简单描述该方法的实现功能（可选）.
	 *
	 * @see AbstractRoutingDataSource
	 */
	protected Object determineCurrentLookupKey() {

		return DynamicDataSourceHolder.getDataSouce();
	}

	public void init() {
		instance = this;
		if (null == targetDataSources || targetDataSources.isEmpty()) {
			setDataSources();
		}
//		scheduExec = Executors.newScheduledThreadPool(1);
//		Runnable pool = new ScheduledDataSourcePool();
//		scheduExec.schedule(pool, 10 * 60 * 1000, TimeUnit.MILLISECONDS);
	}

	private void setDataSources() {
		sysEname = StrUtil.doNull(sysEname, "default");
		if (null == targetDataSources) {
			targetDataSources = new Hashtable<>(1);
		}
		if(enableIncludeRpc){//初始化数据源
			initRpcDataSource();
		}
		if ("baseSource".equalsIgnoreCase(sysEname)) {
			//数据源列表
			Object objKeys = LoadCacheFactory.getInstance().getCacheManager("")
					.getCacheObjectByKey("rpc_datasource_keys");
			String sources = "";
			if (null != objKeys) {
				sources = String.valueOf(objKeys);
				String ds[] = sources.split(",");
				for (int i = 0; i < ds.length; i++) {
					if (StrUtil.isNotNull(ds[i])) {
						RpcDatasource rds = (RpcDatasource) LoadCacheFactory.getInstance().getCacheManager("")
								.getCacheObjectByKey("rpc_datasource_" + ds[i]);
						DruidDataSource dsource = getBasicDataSource(rds.getDriverClassName(), rds.getJdbcUrl(),
								rds.getJdbcUsername(), rds.getJdbcPassword(), rds.getDatabaseType());
						targetDataSources.put(rds.getSysEname(), dsource);
						if ("default".equalsIgnoreCase(rds.getDataName())
								|| "plateform".equalsIgnoreCase(rds.getSysEname())) {
							this.setDefaultTargetDataSource(dsource);
							defaultDataSourceKey = rds.getSysEname();
						}
					}
				}
			}
		} else {
			String sys[] = sysEname.split(",");
			for (int i = 0; i < sys.length; i++) {
				String key = sys[i];
				boolean isDatasource = LoadCacheFactory.getInstance().getCacheManager("")
						.checkCacheObject("rpc_datasource_" + key);
				if (isDatasource) {
					Object obj = LoadCacheFactory.getInstance().getCacheManager("")
							.getCacheObjectByKey("rpc_datasource_" + key);
					RpcDatasource rds = (RpcDatasource) obj;
					String className = rds.getDriverClassName();
					String url = rds.getJdbcUrl();
					String user = rds.getJdbcUsername();
					String pwd = rds.getJdbcPassword();
					key = rds.getSysEname();
					DruidDataSource dsss = getBasicDataSource(className, url, user, pwd, rds.getDatabaseType());
					targetDataSources.put(key, dsss);
					if (i == 0) {
						this.setDefaultTargetDataSource(dsss);
						defaultDataSourceKey = key;
					}
				} else {
					throw new IllegalArgumentException("加载数据源失败：数据源[" + key + "]缓存中不存在");
				}
			}
		}
	}

   private void initRpcDataSource(){
		try{
			LoadCacheFactory.getInstance().getCacheManager("").createCacheObject("rpc_datasource_"+rpcDb.getSysEname(),rpcDb);
			DruidDataSource dsource = getBasicDataSource(rpcDb.getDriverClassName(), rpcDb.getJdbcUrl(),
					rpcDb.getJdbcUsername(), rpcDb.getJdbcPassword(), rpcDb.getDatabaseType());
			targetDataSources.put(rpcDb.getSysEname(), dsource);
			StringBuilder sql = new StringBuilder("select data_name,sys_ename,driver_class_name,jdbc_url,jdbc_username,jdbc_password,database_type,datasource1,datasource2 from t_rpc_datasource");
			String str[] = null;
			if (!"baseSource".equalsIgnoreCase(sysEname)){
				str = sysEname.split(",");
				sql.append(" where sys_ename in(");
				sql.append("'"+str[0]+"'");
				for (int i = 1; i < str.length; i++) {
					sql.append(",'"+str[0]+"'");
				}
				sql.append(")");
				Connection conn = dsource.getConnection();
				List<Map> list = ConnctionData.getInstance().selectListInfo(conn,sql.toString());
				if(Validator.isEmpty(list)){
					return ;
				}
				for(int i=0;i<list.size();i++){
					Map<String,Object> map = list.get(i);
					RpcDatasource rds = new RpcDatasource();
					rds = (RpcDatasource)ClassToolsUtil.getInstance().convert2Bean(map,rds);
					LoadCacheFactory.getInstance().getCacheManager("").createCacheObject("rpc_datasource_"+rds.getSysEname(),rds);
				}
			}
		}catch (Exception e){

		}
   }

	private DruidDataSource getBasicDataSource(String className, String url, String user, String pwd, String dataType) {
		DruidDataSource dsss = new DruidDataSource();
		dsss.setDriverClassName(className);
		dsss.setUrl(url);
		dsss.setUsername(user);
		dsss.setPassword(pwd);
		/**
		 * 最大活动连接数
		 * 同时进行的数据库连接数不超过这个数目
		 * */
		dsss.setMaxActive(maxActive);
		dsss.setInitialSize(initialSize);
		/**
		 * 最大空闲连接数
		 * 当释放数据库连接后,空闲连接数超过这个数目时关闭一些空闲连接
		 */
		//dsss.setMaxIdle(maxIdle);
		dsss.setMinIdle(minIdle);
		dsss.setMaxWait(180000);//最大等待时间，当没有可用连接时，连接池等待连接释放的最大时间
		/**
		 * 是否预编译SQL语句
		 * */
		dsss.setPoolPreparedStatements(true);
//		dsss.setTestWhileIdle(true);
		//是否自动回收超时连接
		dsss.setRemoveAbandoned(true);
		//超时时间(以秒数为单位)
		dsss.setRemoveAbandonedTimeout(300);
		//取得连接时是否进行有效性验证（即是否还和数据库连通的）
		dsss.setTestOnBorrow(true);
		//返回连接时是否进行有效性验证（即是否还和数据库连通的）
		dsss.setTestOnReturn(false);

		if ("oracle".equalsIgnoreCase(dataType)) {
			dsss.setValidationQuery("select 1 from dual");
		} else {
			dsss.setValidationQuery("select 1 ");
		}
		/**
		 * 他们两个配合，可以持续更新连接池中的连接对象，当timeBetweenEvictionRunsMillis 大于0时，每过timeBetweenEvictionRunsMillis 时间，
		 * 就会启动一个线程，校验连接池中闲置时间超过minEvictableIdleTimeMillis的连接对象。
		 * */
		dsss.setTimeBetweenEvictionRunsMillis(3);
		dsss.setMinEvictableIdleTimeMillis(60 * 60 * 1000);
		//dsss.setDefaultAutoCommit(defaultAutoCommit);
		return dsss;
	}

	public synchronized boolean removeDataSource(String key) {
		if (Validator.isNull(key) || "default".equalsIgnoreCase(key) || "plateform".equalsIgnoreCase(key))
			return false;
		if(null != resolvedDataSources.get(key)){
			((DruidDataSource)resolvedDataSources.get(key)).close();
		}
		targetDataSources.remove(key);
		resolvedDataSources.remove(key);
		dynamicTimes.remove(key);
//		ConnctionData.getInstance().delBasicDataSource(key);
		return true;
	}

	public synchronized boolean addDataSource(String sourceKey, String className, String url, String user, String pwd) {
		if (StrUtil.isNull(sourceKey) || StrUtil.isNull(className) || StrUtil.isNull(url) || StrUtil.isNull(user)
				|| StrUtil.isNull(pwd)) {
			return false;
		}
		DruidDataSource dsource = getBasicDataSource(className, url, user, pwd,
				ConnctionData.getInstance().getDataBaseType(null, className, ""));
		targetDataSources.put(sourceKey, dsource);
		setResolveDataSource();
		dynamicTimes.put(sourceKey, System.currentTimeMillis());
		return true;
	}

	private void setResolveDataSource() {
		if (this.targetDataSources == null || targetDataSources.isEmpty()) {
			throw new IllegalArgumentException("Property 'targetDataSources' is required");
		}
		for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
			Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
			if(!this.resolvedDataSources.containsKey(lookupKey)){
				DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
				this.resolvedDataSources.put(lookupKey, dataSource);
			}
		}
	}

	public synchronized boolean addDataSource(String sysCode) {
		if (StrUtil.isNull(sysCode)) {
			return false;
		}
		Object obj = LoadCacheFactory.getInstance().getCacheManager("")
				.getCacheObjectByKey("rpc_datasource_" + sysCode);
		if (null == obj) {
			return false;
		}
		RpcDatasource rds = (RpcDatasource) obj;
		return addDataSource(rds.getSysEname(), rds.getDriverClassName(), rds.getJdbcUrl(), rds.getJdbcUsername(),
				rds.getJdbcPassword());
	}

	public DruidDataSource getDataSource(String sysName) {
		if (!checkDataSource(sysName)) {
			addDataSource(sysName);
		}
		return (DruidDataSource) resolvedDataSources.get(sysName);
	}

	public String getDefaultDataSourceKey(){
		return defaultDataSourceKey;
	}

	public boolean checkDataSource(String sysCode) {
		if(targetDataSources==null)
			return false;
		return targetDataSources.containsKey(sysCode);
	}

	public synchronized void updteTime(String sysCode) {
		if (dynamicTimes.containsKey(sysCode)) {
			dynamicTimes.put(sysCode, System.currentTimeMillis());
		}
	}

	public void setSysEname(String sysEname) {
		this.sysEname = sysEname;
	}

	public void setMaxActive(int maxActive) {
		this.maxActive = maxActive;
	}

	public void setMaxIdle(int maxIdle) {
		this.maxIdle = maxIdle;
	}

	public void setMinIdle(int minIdle) {
		this.minIdle = minIdle;
	}

	public void setInitialSize(int initialSize) {
		this.initialSize = initialSize;
	}

	public void setDefaultAutoCommit(boolean defaultAutoCommit) {
		this.defaultAutoCommit = defaultAutoCommit;
	}

	public void setTargetDataSources(Map<Object, Object> targetDataSources) {
		this.targetDataSources = targetDataSources;
	}

	public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
		this.defaultTargetDataSource = defaultTargetDataSource;
	}

	public void setLenientFallback(boolean lenientFallback) {
		this.lenientFallback = lenientFallback;
	}

	public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {
		this.dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup());
	}

	public boolean getEnableIncludeRpc() {
		return enableIncludeRpc;
	}

	public void setEnableIncludeRpc(boolean enableIncludeRpc) {
		this.enableIncludeRpc = enableIncludeRpc;
	}

	public void setRpcDb(RpcDatasource rpcDb) {
		if(null != rpcDb){
			enableIncludeRpc = true;
		}
		this.rpcDb = rpcDb;
	}

	public RpcDatasource getRpcDb() {
		return rpcDb;
	}

	@Override
	public void afterPropertiesSet() {
		if (StrUtil.isNull(sysEname)) {
			setResolveSpecified();
			return;
		}
		if (targetDataSources == null || targetDataSources.isEmpty()) {
			setDataSources();
			setResolveSpecified();
		}
	}

	private void setResolveSpecified() {
		if (this.targetDataSources == null || targetDataSources.isEmpty()) {
			throw new IllegalArgumentException("Property 'targetDataSources' is required");
		}
		this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
		for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
			Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
			DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
			this.resolvedDataSources.put(lookupKey, dataSource);
		}
		if (this.defaultTargetDataSource != null) {
			this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
		}
	}

	protected Object resolveSpecifiedLookupKey(Object lookupKey) {
		return lookupKey;
	}

	protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
		if (dataSource instanceof DataSource) {
			return (DataSource) dataSource;
		} else if (dataSource instanceof String) {

			return this.dataSourceLookup.getDataSource((String) dataSource);
		} else {
			throw new IllegalArgumentException(
					"Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
		}
	}

	@Override
	public Connection getConnection() throws SQLException {
		return determineTargetDataSource().getConnection();
	}

	@Override
	public Connection getConnection(String username, String password) throws SQLException {
		return determineTargetDataSource().getConnection(username, password);
	}

	@Override
	@SuppressWarnings("unchecked")
	public <T> T unwrap(Class<T> iface) throws SQLException {
		if (iface.isInstance(this)) {
			return (T) this;
		}
		return determineTargetDataSource().unwrap(iface);
	}

	@Override
	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		return (iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface));
	}

	protected DataSource determineTargetDataSource() {
		Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
		Object lookupKey = determineCurrentLookupKey();
		String key = String.valueOf(lookupKey);
		DataSource dataSource = this.resolvedDataSources.get(lookupKey);
		if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
			dataSource = this.resolvedDefaultDataSource;
			Object obj = LoadCacheFactory.getInstance().getCacheManager("")
					.getCacheObjectByKey("rpc_datasource_" + defaultDataSourceKey);
			if (null != obj) {
				RpcDatasource rds = (RpcDatasource) obj;
				DruidDataSource bds = (DruidDataSource) dataSource;
				String caheKey = bds.getDriverClassName() + bds.getUrl() + bds.getUsername() + bds.getPassword();
				String cacheStr = rds.getDriverClassName() + rds.getJdbcUrl() + rds.getJdbcUsername()
						+ rds.getJdbcPassword();
				if (!caheKey.equals(cacheStr)) {
					addDataSource(defaultDataSourceKey);
					this.resolvedDefaultDataSource = resolvedDataSources.get("defaultDataSourceKey");
					defaultTargetDataSource = this.resolvedDefaultDataSource;
					dataSource = this.resolvedDefaultDataSource;
					//					ConnctionData.getInstance().setBasicDataSource(key,
					//							(DruidDataSource) this.resolvedDefaultDataSource);
				}
			}
		} else {
			Object obj = LoadCacheFactory.getInstance().getCacheManager("")
					.getCacheObjectByKey("rpc_datasource_" + key);
			if (null != obj) {
				RpcDatasource rds = (RpcDatasource) obj;
				DruidDataSource bds = (DruidDataSource) dataSource;
				String caheKey = bds.getDriverClassName() + bds.getUrl() + bds.getUsername() + bds.getPassword();
				String cacheStr = rds.getDriverClassName() + rds.getJdbcUrl() + rds.getJdbcUsername()
						+ rds.getJdbcPassword();
				if (!caheKey.equals(cacheStr)) {
					addDataSource(key);
					dataSource = this.resolvedDataSources.get(lookupKey);
					//					ConnctionData.getInstance().setBasicDataSource(key, (DruidDataSource) dataSource);
				}
			}
		}
		if (dataSource == null) {
			throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
		}
		return dataSource;
	}

	private class ScheduledDataSourcePool implements Runnable {

		public void run() {
			synchronized (dynamicTimes) {
				if (dynamicTimes.isEmpty())
					return;
				Iterator<Map.Entry<String, Long>> it = dynamicTimes.entrySet().iterator();
				long nowTime = System.currentTimeMillis();
				while (it.hasNext()) {
					Map.Entry<String, Long> entry = it.next();
					long longTime = entry.getValue();
					if ((nowTime - longTime) > 30 * 60000) {
						if(null != resolvedDataSources.get(entry.getKey())){
							((DruidDataSource)resolvedDataSources.get(entry.getKey())).close();
						}
						targetDataSources.remove(entry.getKey());
						resolvedDataSources.remove(entry.getKey());
						it.remove();
					}
				}
			}
		}
	}
}
